package org.smartboot.compare;

import org.smartboot.compare.constants.CompareConstants;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author qinluo
 * @version 1.0.0
 * @since 2019-05-27 16:49
 */
public class ComparatorContext<T> {

    /**
     * Compare request id.
     *
     * @since 1.0.7
     */
    protected String id;

    /**
     * Expect Object
     */
    protected Object expect;

    /**
     * Compare Object
     */
    protected Object actual;

    /**
     * The compare path. such as obj.field.subField.
     */
    protected Path path = Path.ROOT;

    /**
     * Current compare depth.
     */
    protected int depth;

    /**
     * Max depth recorder.
     *
     * @since 1.0.7
     */
    protected AtomicInteger maxDepth;

    /**
     * Debug or useful messages.
     *
     * @since 1.0.7
     */
    protected List<String> messages;

    /**
     * Debug or useful group messages.
     *
     * @since 1.1.1
     */
    protected Map<String, List<String>> groupMessages;

    /**
     * Ignored fields.
     *
     * @since 1.0.7
     */
    protected List<String> skippedFields;

    /**
     * Recycle counter.
     *
     * @since 1.0.7
     */
    protected AtomicInteger recycleCnt;

    /**
     * Recycle checker
     */
    protected RecycleChecker recycleChecker;

    /**
     * The field filters
     */
    protected FieldFilters filters;
    protected IgnoreFieldFilter iff;

    /**
     * Comparator options in bits.
     *
     * @since 1.0.7
     */
    protected long options;

    protected Map<String, Object> extensions;

    /**
     * The configuration interface.
     *
     * @since 1.1.1
     */
    protected Configuration configuration;

    /**
     * The feature functions.
     *
     * @since 1.1.1
     */
    protected FeatureFunction featureFunction = FeatureFunction.DEFAULT;

    public ComparatorContext() {
        this(0, true);
    }

    public ComparatorContext(long options) {
        this(options, true);
    }

    private ComparatorContext(long options, boolean initFields) {
        this.options = options;
        if (initFields) {
            this.id = UUID.randomUUID().toString();
            this.extensions = new HashMap<>(0);
            this.iff = new IgnoreFieldFilter();
            this.filters = new FieldFilters();
            this.recycleChecker = new RecycleChecker();
            this.recycleCnt = new AtomicInteger(0);
            this.skippedFields = new ArrayList<>(16);
            this.groupMessages = new HashMap<>(0);
            this.messages = new ArrayList<>(16);
            this.maxDepth = new AtomicInteger(0);
        }
    }

    public long getOptions() {
        return options;
    }

    public void setOptions(long options) {
        this.options = options;
    }

    public boolean hasOption(Option option) {
        return Option.checkOption(options, option)
                && this.isEffectOption(option);
    }

    public RecycleChecker getRecycleChecker() {
        return recycleChecker;
    }

    public void setIgnoreFields(List<IgnoreField> ignoreFields) {
        /* update iff's ignored fields */
        iff.updateIgnoreFields(ignoreFields);
        filters.register(iff);
    }

    public void registerFieldFilters(FieldFilter ...registeredFilters) {
        if (registeredFilters == null) {
            return;
        }
        for (FieldFilter f : registeredFilters) {
            if (f != null) {
                filters.register(f);
            }
        }
    }

    public Object getExpect() {
        return expect;
    }

    public void setExpect(Object expect) {
        this.expect = expect;
    }

    public Object getActual() {
        return actual;
    }

    public void setActual(Object actual) {
        this.actual = actual;
    }

    public Path getPath() {
        return path;
    }

    public void setPath(Path path) {
        this.path = path;
    }

    public int getDepth() {
        return depth;
    }

    public Path createFieldPath(Field field) {
        return createPath(Path.FIELD, field.getName(), field);
    }

    public Path createKeyPath(Object key) {
        return createPath(Path.KEY, "" + key, key);
    }

    public Path createIndexPath(int index) {
        return createPath(Path.INDEX, CompareConstants.arrayIndex(index), index);
    }

    public Path createPath(int type, String value, Object related) {
        return new Path(path, type, value, related);
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public void setConfiguration(Configuration configuration) {
        this.configuration = configuration;
    }

    public FeatureFunction getFeatureFunction() {
        return featureFunction;
    }

    public void setFeatureFunction(FeatureFunction featureFunction) {
        // ensure feature function is valid.
        if (featureFunction == null) {
            return;
        }

        this.featureFunction = featureFunction;
    }

    public ComparatorContext<Object> clone(Object expect, Object actual) {
        ComparatorContext<Object> context = new ComparatorContext<>(options, false);
        context.expect = expect;
        context.actual = actual;
        context.filters = this.filters;
        context.depth = this.depth;
        context.recycleChecker = recycleChecker;
        context.path = this.path;
        context.maxDepth = this.maxDepth;
        context.messages = this.messages;
        context.skippedFields = this.skippedFields;
        context.recycleCnt = this.recycleCnt;
        context.id = this.id;
        context.extensions = this.extensions;
        context.groupMessages = this.groupMessages;
        context.featureFunction = this.featureFunction;
        context.configuration = this.configuration;
        return context;
    }

    public void incr() {
        this.depth++;
        if (this.depth > maxDepth.get()) {
            maxDepth.set(this.depth);
        }
    }

    public FieldFilters getFilters() {
        return filters;
    }

    public String getId() {
        return id;
    }

    public int getMaxDepth() {
        return maxDepth.get();
    }

    public List<String> getMessages() {
        return messages;
    }

    public List<String> getSkippedFields() {
        return skippedFields;
    }

    public Map<String, List<String>> getGroupMessages() {
        return groupMessages;
    }

    public void addMessage(String msg) {
        this.messages.add(msg);
    }

    public void addMessage(String group, String msg) {
        List<String> messages = groupMessages.computeIfAbsent(group, k -> new ArrayList<>(8));
        if (!messages.contains(msg)) {
            messages.add(msg);
        }
    }

    public void addSkippedField(String field) {
        this.skippedFields.add(field);
    }

    public void incrRecycle() {
        this.recycleCnt.incrementAndGet();
    }

    public int getRecycleCnt() {
        return recycleCnt.get();
    }

    public void setId(String id) {
        this.id = id;
    }

    /* Extension methods.*/
    public <S> S getExt(String key) {
        return (S)extensions.get(key);
    }

    public <S> S removeExt(String key) {
        return (S)extensions.remove(key);
    }

    public <S, R> S addExt(String key, R newExt) {
        return (S)extensions.put(key, newExt);
    }

    /**
     * Option是否生效
     *
     * @param option option
     * @return       effect
     */
    public boolean isEffectOption(Option option) {
        Boolean isEffected = this.featureFunction.isEffectOption(this, option);
        return isEffected == null || isEffected;
    }

    /* Static methods */
    public static ComparatorContext<Object> of(Object expect, Object actual) {
        return of(expect, actual, 0);
    }

    public static ComparatorContext<Object> of(Object expect, Object actual, long options) {
        ComparatorContext<Object> ctx = new ComparatorContext<>(options);
        ctx.setExpect(expect);
        ctx.setActual(actual);
        return ctx;
    }
}
